﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.RegularExpressions;

namespace XNET.Extension
{
    /// <summary>
    /// 通用对象扩展
    /// </summary>
    public static class GenericObjectExtension
    {
        /// <summary>
        /// 判断对象是否为空
        /// </summary>
        /// <param name="obj">对象</param>
        /// <returns></returns>
        public static bool IsNullOrEmpty(this object obj)
        {
            if (obj == null || obj == DBNull.Value) return true;

            return false;
        }

        /// <summary>
        /// Allows actions to be chained together with the caveat that if Object is null, it is
        /// replaced with the DefaultObjectValue specified. If the Action or Object (once replaced
        /// with the default object value) is null, it will return the object.
        /// </summary>
        /// <typeparam name="T">The type of the object</typeparam>
        /// <param name="Object">Object to run the action on</param>
        /// <param name="Action">Action to run</param>
        /// <param name="DefaultObjectValue">Default object value</param>
        /// <returns>The original object</returns>
        public static T Chain<T>(this T Object, Action<T> Action, T DefaultObjectValue = default(T))
        {
            Object = Object.Check(DefaultObjectValue);
            if (Action == null || Object == null)
                return Object;
            Action(Object);
            return Object;
        }

        /// <summary>
        /// Allows actions to be chained together. It also has a couple of checks in there:
        /// 1) If the function is null, it returns the default return value specified.
        /// 2) If the object is null, it will replace it with the default object value specified.
        /// 3) If the object, once replaced with the default object value specified, is null, it
        ///    will return the default return value specified.
        /// 4) If the return value from the function is null, it returns the default return value specified.
        /// </summary>
        /// <typeparam name="T">The type of the object</typeparam>
        /// <typeparam name="R">Return type</typeparam>
        /// <param name="Object">Object to run the action on</param>
        /// <param name="Function">Function to run</param>
        /// <param name="DefaultObjectValue">Default object value</param>
        /// <param name="DefaultReturnValue">Default return value</param>
        /// <returns>The result from the function</returns>
        public static R Chain<T, R>(this T Object, Func<T, R> Function, R DefaultReturnValue = default(R), T DefaultObjectValue = default(T))
        {
            Object = Object.Check(DefaultObjectValue);
            if (Function == null || Object == null)
                return DefaultReturnValue;
            return Function(Object).Check(DefaultReturnValue);
        }

        /// <summary>
        /// 检查对象是否为空，如果是返回默认值，如果不是返回对象
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="Object">要检查的对象</param>
        /// <param name="DefaultValue">默认值</param>
        /// <returns></returns>
        public static T Check<T>(this T Object, T DefaultValue = default(T))
        {
            return Object.Check(x => x != null, DefaultValue);
        }
        /// <summary>
        /// 检查对象是否为空，如果是返回默认值，如果不是返回对象
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="Object">要检查的对象</param>
        /// <param name="DefaultValue">默认对象</param>
        /// <returns></returns>
        public static T Check<T>(this T Object, Func<T> DefaultValue)
        {
            return Object.Check(x => x != null, DefaultValue);
        }
        /// <summary>
        /// 检查对象是否符合要求，符合则返回对象，不符合则返回默认值
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="Object">要检查的对象</param>
        /// <param name="Predicate">检查对象是否符合</param>
        /// <param name="DefaultValue">返回值</param>
        /// <returns></returns>
        public static T Check<T>(this T Object, Predicate<T> Predicate, T DefaultValue = default(T))
        {
            return Predicate(Object) ? Object : DefaultValue;
        }
        /// <summary>
        /// 检查对象是否符合要求，符合则返回对象，不符合则返回默认对象
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="Object">要检查的对象</param>
        /// <param name="Predicate">检查对象是否符合</param>
        /// <param name="DefaultValue">返回对象</param>
        /// <returns></returns>
        public static T Check<T>(this T Object, Predicate<T> Predicate, Func<T> DefaultValue)
        {
            return Predicate(Object) ? Object : DefaultValue();
        }
        /// <summary>
        /// Determines if the object passes the predicate passed in
        /// </summary>
        /// <typeparam name="T">Object type</typeparam>
        /// <param name="Object">Object to test</param>
        /// <param name="Predicate">Predicate to test</param>
        /// <returns>True if the object passes the predicate, false otherwise</returns>
        public static bool Is<T>(this T Object, Predicate<T> Predicate)
        {
            return Predicate(Object);
        }

        /// <summary>
        /// Throws the specified exception if the predicate is true for the item
        /// </summary>
        /// <typeparam name="T">Item type</typeparam>
        /// <param name="Item">The item</param>
        /// <param name="Predicate">Predicate to check</param>
        /// <param name="Exception">Exception to throw if predicate is true</param>
        /// <returns>the original Item</returns>
        public static T ThrowIf<T>(this T Item, Predicate<T> Predicate, Func<Exception> Exception)
        {
            if (Predicate(Item))
            {
                throw Exception();
            }
            return Item;
        }

        /// <summary>
        /// Throws the specified exception if the predicate is true for the item
        /// </summary>
        /// <typeparam name="T">Item type</typeparam>
        /// <param name="Item">The item</param>
        /// <param name="Predicate">Predicate to check</param>
        /// <param name="Exception">Exception to throw if predicate is true</param>
        /// <returns>the original Item</returns>
        public static T ThrowIf<T>(this T Item, Predicate<T> Predicate, Exception Exception)
        {
            if (Predicate(Item))
            {
                throw Exception;
            }
            return Item;
        }


        /// <summary>
        /// Throws the specified exception if the predicate is false for the item
        /// </summary>
        /// <typeparam name="T">Item type</typeparam>
        /// <param name="Item">The item</param>
        /// <param name="Predicate">Predicate to check</param>
        /// <param name="Exception">Exception to throw if predicate is false</param>
        /// <returns>the original Item</returns>
        public static T ThrowIfNot<T>(this T Item, Predicate<T> Predicate, Exception Exception)
        {
            return Item.ThrowIf(x => !Predicate(x), Exception);
        }

        /// <summary>
        /// Determines if the object is not null and throws an ArgumentException if it is
        /// </summary>
        /// <param name="Item">The object to check</param>
        /// <param name="Name">Name of the argument</param>
        /// <returns>Returns Item</returns>
        public static T ThrowIfNotNull<T>(this T Item, string Name)
        {
            return Item.ThrowIfNotNull(new ArgumentException(Name));
        }

        /// <summary>
        /// Determines if the object is not null and throws the exception passed in if it is
        /// </summary>
        /// <param name="Item">The object to check</param>
        /// <param name="Exception">Exception to throw</param>
        /// <returns>Returns Item</returns>
        public static T ThrowIfNotNull<T>(this T Item, Exception Exception)
        {
            return Item.ThrowIf(x => x != null && !Convert.IsDBNull(x), Exception);
        }

        /// <summary>
        /// Determines if the object is null and throws an ArgumentNullException if it is
        /// </summary>
        /// <param name="Item">The object to check</param>
        /// <param name="Name">Name of the argument</param>
        /// <returns>Returns Item</returns>
        public static T ThrowIfNull<T>(this T Item, string Name)
        {
            return Item.ThrowIfNull(new ArgumentNullException(Name));
        }

        /// <summary>
        /// Determines if the object is null and throws the exception passed in if it is
        /// </summary>
        /// <param name="Item">The object to check</param>
        /// <param name="Exception">Exception to throw</param>
        /// <returns>Returns Item</returns>
        public static T ThrowIfNull<T>(this T Item, Exception Exception)
        {
            return Item.ThrowIf(x => x == null || Convert.IsDBNull(x), Exception);
        }

        /// <summary>
        /// Determines if an object is of a specific type
        /// </summary>
        /// <param name="Object">Object</param>
        /// <typeparam name="BaseObjectType">Base object type</typeparam>
        /// <returns>True if it is, false otherwise</returns>
        public static bool Is<BaseObjectType>(this object Object)
        {
            return Object.Is(typeof(BaseObjectType));
        }

        /// <summary>
        /// 确定类型的默认构造函数
        /// </summary>
        /// <param name="Object">对象</param>
        /// <param name="Type">类型</param>
        /// <returns></returns>
        public static bool Is(this object Object, Type Type)
        {
            return Object.GetType().Is(Type);
        }
        /// <summary>
        /// 判断对象是否数字
        /// </summary>
        /// <param name="value">值</param>
        /// <returns></returns>
        public static bool IsNumeric(this object value)
        {
            if (value == null)
            {
                return false;
            }
            var rex = new Regex(@"^\d+$");
            if (rex.IsMatch(value.ToString()))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 调用对象的ToString方法格式化字符串
        /// </summary>
        /// <param name="Input">输入对象</param>
        /// <param name="Format">格式化输出字符串</param>
        /// <returns></returns>
        public static string FormatToString(this object Input, string Format)
        {
            if (Input == null)
            {
                return string.Empty;
            }
            return !string.IsNullOrEmpty(Format) ? Input.Call<string>("ToString", Format) : Input.ToString();
        }
        /// <summary>
        /// 调用一个对象的方法
        /// </summary>
        /// <param name="MethodName">方法名</param>
        /// <param name="Object">调用方法的对象</param>
        /// <param name="InputVariables">方法参数</param>
        /// <typeparam name="ReturnType">返回类型</typeparam>
        /// <returns></returns>
        public static ReturnType Call<ReturnType>(this object Object, string MethodName, params object[] InputVariables)
        {
            if (InputVariables == null)
            {
                InputVariables = new object[0];
            }
            var ObjectType = Object.GetType();
            var MethodInputTypes = new Type[InputVariables.Length];
            for (var x = 0; x < InputVariables.Length; ++x)
            {
                MethodInputTypes[x] = InputVariables[x].GetType();
            }
            var Method = ObjectType.GetMethod(MethodName, MethodInputTypes);
            if (Method == null)
            {
                throw new InvalidOperationException("没有找到方法 " + MethodName + " 和相应输入的变量.");
            }
            return (ReturnType)Method.Invoke(Object, InputVariables);
        }

        /// <summary>
        /// 通用对象转换 支持枚举，?类型
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="obj">对象</param>
        /// <param name="defaultValue">默认值</param>
        /// <returns>返回指定类型的对象</returns>
        public static T GetValue<T>(this object obj, T defaultValue)
        {
            if (obj == null || obj == DBNull.Value)
            {
                return defaultValue;
            }
            else if (typeof(T).IsEnum)
            {
                return (T)Enum.ToObject(typeof(T), obj);
            }
            else
            {
                if (!typeof(T).IsGenericType)
                {
                    return (T)Convert.ChangeType(obj, typeof(T));
                }
                else
                {
                    Type genericTypeDefinition = typeof(T).GetGenericTypeDefinition();
                    if (genericTypeDefinition == typeof(Nullable<>))
                    {
                        return (T)Convert.ChangeType(obj, Nullable.GetUnderlyingType(typeof(T)));
                    }
                    else
                    {
                        return defaultValue;
                    }
                }
            }
        }

        /// <summary>
        /// 获取属性值
        /// </summary>
        /// <param name="Object">对象</param>
        /// <param name="Property">属性</param>
        /// <returns></returns>
        public static object Property(this object Object, PropertyInfo Property)
        {
            return Property.GetValue(Object, null);
        }

        /// <summary>
        /// 获取属性值
        /// </summary>
        /// <param name="Object">对象</param>
        /// <param name="Property">属性</param>
        /// <returns></returns>
        public static object Property(this object Object, string Property)
        {
            var Properties = Property.Split(new string[] { "." }, StringSplitOptions.None);
            var TempObject = Object;
            var TempObjectType = TempObject.GetType();
            PropertyInfo DestinationProperty = null;
            for (var x = 0; x < Properties.Length - 1; ++x)
            {
                DestinationProperty = TempObjectType.GetProperty(Properties[x]);
                TempObjectType = DestinationProperty.PropertyType;
                TempObject = DestinationProperty.GetValue(TempObject, null);
                if (TempObject == null)
                {
                    return null;
                }
            }
            DestinationProperty = TempObjectType.GetProperty(Properties[Properties.Length - 1]);
            return TempObject.Property(DestinationProperty);
        }

        /// <summary>
        /// 获取属性类型
        /// </summary>
        /// <param name="Object">对象</param>
        /// <param name="PropertyPath">属性</param>
        /// <returns></returns>
        public static Type PropertyType(this object Object, string PropertyPath)
        {
            if (Object == null || string.IsNullOrEmpty(PropertyPath))
            {
                return null;
            }
            return Object.GetType().PropertyType(PropertyPath);
        }

        /// <summary>
        /// 格式化对象属性名称及属性值
        /// </summary>
        /// <param name="Object">对象</param>
        /// <param name="HTMLOutput">是否输出HTML格式</param>
        /// <returns></returns>
        public static string ToString(this object Object, bool HTMLOutput)
        {
            var TempValue = new StringBuilder();
            TempValue.Append(HTMLOutput ? "<table><thead><tr><th>属性名称</th><th>属性值</th></tr></thead><tbody>" : "属性名称\t\t\t\t属性值");
            var ObjectType = Object.GetType();
            foreach (PropertyInfo Property in ObjectType.GetProperties())
            {
                TempValue.Append(HTMLOutput ? "<tr><td>" : string.Empty).Append(Property.Name).Append(HTMLOutput ? "</td><td>" : "\t\t\t\t");
                var Parameters = Property.GetIndexParameters();
                if (Property.CanRead && Parameters.Length == 0)
                {
                    try
                    {
                        var Value = Property.GetValue(Object, null);
                        TempValue.Append(Value == null ? "null" : Value.ToString());
                    }
                    catch
                    {
                    }
                }
                TempValue.Append(HTMLOutput ? "</td></tr>" : string.Empty);
            }
            TempValue.Append(HTMLOutput ? "</tbody></table>" : string.Empty);
            return TempValue.ToString();
        }

        /// <summary>
        /// 对象转换为字节
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="obj">字节</param>
        /// <returns></returns>
        public static byte[] ObjectToBytes(this object obj)
        {
            if (obj.IsNullOrEmpty()) return null;
            try
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    IFormatter formatter = new BinaryFormatter();
                    formatter.Serialize(ms, obj);
                    return ms.GetBuffer();
                }
            }
            catch
            {
                return null;
            }
        }
    }
}
